﻿using System;
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.ServiceModel;
using Ebt.Linq;

namespace Service.Model
{
	public static class FederatedOData
	{
        private static readonly string[] FederationKeyColumns = new [] { "stateid" };

	    public static void ReleaseDataContext()
		{
			object contextObj;
			OperationContext.Current.IncomingMessageProperties.TryGetValue("EfContext", out contextObj);

			var context = contextObj as EfContext;

			if(context != null)
				context.Dispose();
		}

		#region IQueriable properties implementation stuff

		/// <summary>
		/// Tries to resolve database and returns queryable entities set from appropriate Entity Framework context.
		/// </summary>
		/// <typeparam name="TEntity">Type of entities.</typeparam>
		/// <param name="getEntities">Gets entities from the Entity Framework context.</param>
		/// <returns>Object that allows to perform queries against all data sources at once.</returns>
        public static IQueryable<TEntity> RedirectQueryToFederationMember<TEntity>(Func<EfContext, IQueryable<TEntity>> getEntities)
		{
			return new FederatedQuery<TEntity>(
				atomKey => GetEntityCollection(atomKey, getEntities));
		}
		
		/// <summary>
		/// Gets Entity Framework entities by the specified federation atom key.
		/// </summary>
		/// <typeparam name="TEntity"></typeparam>
		/// <param name="atomKey">If non null then one data context is used. Otherwise all contexts will be used.</param>
		/// <param name="getEntities"></param>
		/// <returns></returns>
        private static IQueryable<TEntity> GetEntityCollection<TEntity>(IEnumerable<PropertyValue> atomKey, Func<EfContext, IQueryable<TEntity>> getEntities)
		{
            if (atomKey == null)
            {
                if(FederationMetadataProvider.IsRootEntity(typeof (TEntity)))
                    return GetRootEntityCollection(getEntities);

                throw new NotSupportedException("Fun out queries are not supported.");
            }

		    var context = GetContext();

		    var i = 0;
		    var useFederationSql = atomKey.Aggregate("USE FEDERATION StateFederation(", (current, value) => current + (FederationKeyColumns[i++] + '=' + value.Value)); // TODO Values can require special formatting.
		    useFederationSql += ")  WITH RESET, FILTERING=ON";

			context.Database.ExecuteSqlCommand(useFederationSql);

			return getEntities(context);
		}

		private static IQueryable<TEntity> GetRootEntityCollection<TEntity>(Func<EfContext, IQueryable<TEntity>> getEntities)
		{
			var context = GetContext();
			const string useFederationSql = "USE FEDERATION ROOT WITH RESET";
			context.Database.ExecuteSqlCommand(useFederationSql);
			return getEntities(context);
		}

		private static EfContext GetContext()
		{
			object contextObj;
			OperationContext.Current.IncomingMessageProperties.TryGetValue("EfContext", out contextObj);

			var context = contextObj as EfContext;

			if (context == null && contextObj != null)
				throw new InvalidOperationException("EfContext expected.");

			if (context == null)
			{
				context = new EfContext();
				((IObjectContextAdapter)context).ObjectContext.Connection.Open();
				OperationContext.Current.IncomingMessageProperties["EfContext"] = context;
			}
			return context;
		}

		#endregion
	}
}